package de.justphil.tcg.tcgserver.commons.grid;

import java.util.Collection;
import java.util.concurrent.locks.Lock;

import org.slf4j.Logger;

import com.hazelcast.config.MapConfig;
import com.hazelcast.config.MapStoreConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.Instance;
import com.hazelcast.core.InstanceEvent;
import com.hazelcast.core.InstanceListener;
import com.hazelcast.core.Transaction;

import static de.justphil.tcg.tcgserver.commons.logging.TCGLogging.*;
import de.justphil.tcg.tcgserver.commons.config.GridConfig;

public class Grid implements InstanceListener {
	private static final Logger log = logger(Grid.class);
	private static final Logger status = statusLogger(Grid.class);
	
	public static void initWebSocketServerGrid() {
		log.info("Initializing WebSocket Server Grid...");
		
		// turn off caching of map values such that a sole Hazelcast node behaves like a real cluster
		// in production this will be turned on in order to increase cluster performance
		com.hazelcast.config.Config cfg = Hazelcast.getConfig();
		
		// 'default' Map
		MapConfig defaultMapCfg = cfg.getMapConfig("default");
		defaultMapCfg.setCacheValue(false);
		
		// GridConfig.GRID_CURRENT_GAMES_BY_ID_MAP
		MapConfig currentGamesMapCfg = new MapConfig();
		currentGamesMapCfg.setName(GridConfig.GRID_CURRENT_GAMES_BY_ID_MAP);
		currentGamesMapCfg.setCacheValue(false);
		cfg.addMapConfig(currentGamesMapCfg);
		
		// GridConfig.GRID_TEST_GAMES
		MapConfig testGamesMapCfg = new MapConfig();
		testGamesMapCfg.setName(GridConfig.GRID_TEST_GAMES);
		testGamesMapCfg.setCacheValue(false);
		cfg.addMapConfig(testGamesMapCfg);
		
		
		// register data structure listener
		registerDataStructureInstanceListener(new Grid());
		log.info("Initializing WebSocket Server Grid...DONE!");
	}
	
	public static void initRestServerGrid() {
		log.info("Initializing REST Server Grid...");
		
		// turn off caching of map values such that a sole Hazelcast node behaves like a real cluster
		// in production this will be turned on in order to increase cluster performance
		com.hazelcast.config.Config cfg = Hazelcast.getConfig();
		
		// GridConfig.GRID_OTP_GAMES
		MapConfig otpGamesMapCfg = new MapConfig();
		otpGamesMapCfg.setName(GridConfig.GRID_OTP_GAMES);
		otpGamesMapCfg.setCacheValue(false);
		cfg.addMapConfig(otpGamesMapCfg);
		log.debug("otpGamesMapCfg set!");
		
		// GridConfig.GRID_DB_USERS
		MapConfig userMapCfg = new MapConfig();
		userMapCfg.setName(GridConfig.GRID_DB_USERS);
		userMapCfg.setCacheValue(false);
		MapStoreConfig mapStoreConfig = new MapStoreConfig();
		mapStoreConfig.setEnabled(true);
		mapStoreConfig.setClassName("de.justphil.tcg.tcgserver.rest.db.mapstores.UserMapStore");
		mapStoreConfig.setWriteDelaySeconds(5);
		userMapCfg.setMapStoreConfig(mapStoreConfig);
		cfg.addMapConfig(userMapCfg);
		log.debug("userMapCfg set!");
		
		// GridConfig.GRID_DB_GAMES
		MapConfig gameMapCfg = new MapConfig();
		gameMapCfg.setName(GridConfig.GRID_DB_GAMES);
		gameMapCfg.setCacheValue(false);
		MapStoreConfig gameMapStoreConfig = new MapStoreConfig();
		gameMapStoreConfig.setEnabled(true);
		gameMapStoreConfig.setClassName("de.justphil.tcg.tcgserver.rest.db.mapstores.GameMapStore");
		gameMapStoreConfig.setWriteDelaySeconds(5);
		gameMapCfg.setMapStoreConfig(gameMapStoreConfig);
		cfg.addMapConfig(gameMapCfg);
		log.debug("gameMapCfg set!");
		
		// GridConfig.GRID_DB_DECKS
		MapConfig deckMapCfg = new MapConfig();
		deckMapCfg.setName(GridConfig.GRID_DB_DECKS);
		deckMapCfg.setCacheValue(false);
		MapStoreConfig deckMapStoreConfig = new MapStoreConfig();
		deckMapStoreConfig.setEnabled(true);
		deckMapStoreConfig.setClassName("de.justphil.tcg.tcgserver.rest.db.mapstores.DeckMapStore");
		deckMapStoreConfig.setWriteDelaySeconds(5);
		deckMapCfg.setMapStoreConfig(deckMapStoreConfig);
		cfg.addMapConfig(deckMapCfg);
		log.debug("deckMapCfg set!");
		
		// GridConfig.GRID_DB_CARDS
		MapConfig cardMapCfg = new MapConfig();
		cardMapCfg.setName(GridConfig.GRID_DB_CARDS);
		cardMapCfg.setCacheValue(false);
		MapStoreConfig cardMapStoreConfig = new MapStoreConfig();
		cardMapStoreConfig.setEnabled(true);
		cardMapStoreConfig.setClassName("de.justphil.tcg.tcgserver.rest.db.mapstores.CardMapStore");
		cardMapStoreConfig.setWriteDelaySeconds(5);
		cardMapCfg.setMapStoreConfig(cardMapStoreConfig);
		cfg.addMapConfig(cardMapCfg);
		log.debug("cardMapCfg set!");
		
		// GridConfig.GRID_DB_CARD_INSTANCES
		MapConfig ciMapCfg = new MapConfig();
		ciMapCfg.setName(GridConfig.GRID_DB_CARD_INSTANCES);
		ciMapCfg.setCacheValue(false);
		MapStoreConfig ciMapStoreConfig = new MapStoreConfig();
		ciMapStoreConfig.setEnabled(true);
		ciMapStoreConfig.setClassName("de.justphil.tcg.tcgserver.rest.db.mapstores.CardInstanceMapStore");
		ciMapStoreConfig.setWriteDelaySeconds(5);
		ciMapCfg.setMapStoreConfig(ciMapStoreConfig);
		cfg.addMapConfig(ciMapCfg);
		log.debug("ciMapCfg set!");
		
		// GridConfig.GRID_DB_PROPERTIES
		MapConfig propertyMapCfg = new MapConfig();
		propertyMapCfg.setName(GridConfig.GRID_DB_PROPERTIES);
		propertyMapCfg.setCacheValue(false);
		MapStoreConfig propertyMapStoreConfig = new MapStoreConfig();
		propertyMapStoreConfig.setEnabled(true);
		propertyMapStoreConfig.setClassName("de.justphil.tcg.tcgserver.rest.db.mapstores.PropertyMapStore");
		propertyMapStoreConfig.setWriteDelaySeconds(5);
		propertyMapCfg.setMapStoreConfig(propertyMapStoreConfig);
		cfg.addMapConfig(propertyMapCfg);
		log.debug("propertyMapCfg set!");
		
		// register data structure listener
		registerDataStructureInstanceListener(new Grid());
		log.info("Initializing REST Server Grid...DONE!");
	}
	
	public static void shutdownAll() {
		Hazelcast.shutdownAll();
	}
	
	/* ############################################################################################ */
	/* ############################################################################################ */
	/* ############################################################################################ */
	
	public static Lock getCurrentGameLock(Long currentGameId) {
		String lockName = GridConfig.GRID_CURRENT_GAMES_BY_ID_MAP + "-" + currentGameId.longValue();
		log.debug("getCurrentGameLock() => {}", lockName);
		return Hazelcast.getLock(lockName);
	}
	
	public static Lock getCardInstanceLock(Long ciId) {
		String lockName = GridConfig.GRID_DB_CARD_INSTANCES + "-" + ciId.longValue();
		log.debug("getCardInstanceLock() => {}", lockName);
		return Hazelcast.getLock(lockName);
	}
	
	public static Lock getGameLock(Long gameId) {
		String lockName = GridConfig.GRID_DB_GAMES + "-" + gameId.longValue();
		log.debug("getGameLock() => {}", lockName);
		return Hazelcast.getLock(lockName);
	}
	
	/* ######################################################################## */
	/* ######################################################################## */
	/* ######################################################################## */
	
	public static Transaction getTransaction() {
		return Hazelcast.getTransaction();
	}
	
	/* ######################################################################## */
	/* ######################################################################## */
	/* ######################################################################## */

	private static void registerDataStructureInstanceListener(InstanceListener instanceListener) {
		Hazelcast.addInstanceListener(instanceListener);
		
		status.info("### ####################################### ###");
		status.info("### DISTRIBUTED DATA STRUCTURES IN THE GRID ###");
		status.info("### ####################################### ###");
		
		Collection<Instance> instances = Hazelcast.getInstances();
		for (Instance instance : instances) {
			status.info("### {} ### {} ###", instance.getInstanceType(), instance.getId());
		}
	}
	
	@Override
	public void instanceCreated(InstanceEvent ie) {
		Instance instance = ie.getInstance();
		log.info("### Created {} ### {} ###", instance.getInstanceType(), instance.getId());
	}

	@Override
	public void instanceDestroyed(InstanceEvent ie) {
		Instance instance = ie.getInstance();
		log.info("### Destroyed {} ### {} ###", instance.getInstanceType(), instance.getId());
	}

}
